﻿

#region USING
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
//
#endregion USING


namespace Gsp315_AIBehaviors
{
    //Steering Behaviour Types...
    public enum behaviour_Type { SEEK=0, FLEE, ARRIVE, WANDER, PURSUIT /*, ETC*/};
    //Deceleration for Arrive speeds...
    public enum arrive_Speeds { SLOW=3, NORMAL=2, FAST=1 };


    public static class SteeringBehaviours//: ISteeringBehaviour
    {

        private static Random rand = new Random();

        //-------------------------------------------------------------------------
        //SEEK: Returns NEW thetaInRadians, a.k.a: change in direction
        public static float Seek(Agent seeker, Agent target)
        {
            float thetaInRadians = 0.0f;
            Vector2 desired = new Vector2((target.Position.X - seeker.Position.X), (target.Position.Y - seeker.Position.Y));
            desired.Normalize();

            //Normalize only if Vector != Zero
            if (desired != Vector2.Zero) { desired.Normalize(); }

            //thetaInRadians = (float)Math.Acos(Vector2.Dot(seeker.Direction, target.Direction));
            thetaInRadians = MathHelper.WrapAngle((float)Math.Atan2(desired.Y, desired.X));
            
            return ((float)Math.Round(thetaInRadians, 2));
        }



        //-------------------------------------------------------------------------
        //FLEE: Returns NEW thetaInRadians, a.k.a: change in direction
        public static float Flee(Agent fleer, Agent target)
        {
            float thetaInRadians = 0.0f; 
            Vector2 desired = new Vector2((fleer.Position.X - target.Position.X), (fleer.Position.Y - target.Position.Y));
            desired.Normalize();

            //Normalize only if Vector != Zero
            if (desired != Vector2.Zero) { desired.Normalize(); }

            //thetaInRadians = (float)Math.Acos(Vector2.Dot(seeker.Direction, target.Direction));
            thetaInRadians = MathHelper.WrapAngle((float)Math.Atan2(desired.Y, desired.X));

            return ((float)Math.Round(thetaInRadians, 2));
        }



        //-------------------------------------------------------------------------
        //ARRIVE: Returns NEW Agent with updated Position based on how close agent is to target
        //Agent will slow down as it gets closer and will stop once arrving to destination
        public static Agent Arrive(Agent arriver, Agent target)
        {
            float arrivedDistance = arriver.Size.X, startArrive = arriver.Size.X * 3, decelDistance = arriver.Size.X * 2;
            float distance = 0.0f, thetaInRadians = 0.0f;

            //calc Desired direction that arriver should travel towards target
            Vector2 desired = new Vector2((target.Position.X - arriver.Position.X), (target.Position.Y - arriver.Position.Y));
            //distance btw arriver and target
            distance = (float)Math.Sqrt((desired.X * desired.X) + (desired.Y * desired.Y));

            //Normalize only if Vector != Zero
            if (desired != Vector2.Zero) { desired.Normalize(); }

            if (distance <= startArrive)
            {
                thetaInRadians = MathHelper.WrapAngle((float)Math.Atan2(desired.Y, desired.X));
                arriver.AngleInRadians += thetaInRadians;
                arriver.Position += (arriver.Direction * arriver.Speed_Max);
            }
            else if (distance <= decelDistance)
            {
                thetaInRadians = MathHelper.WrapAngle((float)Math.Atan2(desired.Y, desired.X));
                arriver.AngleInRadians += thetaInRadians;
                arriver.Position += (arriver.Direction * arriver.Speed_Min);
            }
            else if (distance <= arrivedDistance)
            {
                thetaInRadians = MathHelper.WrapAngle((float)Math.Atan2(desired.Y, desired.X));
                arriver.AngleInRadians += thetaInRadians;
                arriver.Position += (arriver.Direction * 0.0f);
            }

            //return an Agent w/ updated Position, Angle & Direction
            return arriver;
        }



        //-------------------------------------------------------------------------
        //WANDER Behaviour - recent working but still has a little jitter
        public static float Wander(Agent self)
        {
            float thetaInRadians = 0.0f;
            //Random rand = new Random();

            //returns a random float between -1 & 1...
            //float randClamp = Helper.RandomClamp(rand, 0.0f, 1.0f);

            //Generate a Random vector btw (-1 - 1),  Mu;tiply w/ rotation speed
            self.WanderTarget_Direction = new Vector2(MathHelper.Lerp(-1f, 1f, (float)rand.NextDouble()));  //new Vector2(randClamp * 80f);


            //Normalize this new Random jittered vector
            if (self.WanderTarget_Direction != Vector2.Zero)
            {
                self.WanderTarget_Direction_Normalize();
            }

            //Transform the 'targetDirection' Vector to the wandering circle's parameter
            //This is in local coordinates
            self.WanderTarget_Direction_X *= self.WanderRadius;
            self.WanderTarget_Direction_Y *= self.WanderRadius;

            //Project 'wanderDistance' to self.Center
            Vector2 displacement = new Vector2(self.Direction.X * self.WanderDistance, self.Direction.Y * self.WanderDistance);

            //Transform the 'wanderTarget' position from local to world coordinates
            //WanderDistance is the displacement, how far in front of the agent the wandering circle is placed
            self.WanderTarget_Position = self.Center + self.WanderTarget_Direction + displacement; // new Vector2(self.WanderDistance, 0);

            Vector2 displacement_Desired = self.WanderTarget_Position - self.Center; //self.Position;
            displacement_Desired.Normalize();

            //Calc the target's thetaRadians OR 'Desired Theta'
            float desiredAngle = (float)Math.Atan2(displacement_Desired.Y, displacement_Desired.X);
            //Wrap the theta within 'arcTan' funct boundary
            float difference = MathHelper.WrapAngle(desiredAngle - self.AngleInRadians);

            //Take Change theta and Clamp it within it's rotation speed boundary
            difference = MathHelper.Clamp(difference, -self.Rotation_Min / 2, self.Rotation_Min / 2);
            //Wrap the theta within boundary
            thetaInRadians = MathHelper.WrapAngle(difference);


            //return the theta(radians) that will be used to find the new Direction the agent should move
            return ((float)Math.Round(thetaInRadians, 2));
        }







        //=========================================================================
        //-------------------------------------------------------------------------
        //WANDER: Returns NEW thetaInRadians, a.k.a: change in direction
        //? Very rough and basic WANDER behaviour - JITTERY
        public static float WanderJitter(Agent self)
        {
            float thetaInRadians = 0.0f;
            float jitter = 0.0f; //int isPositive = 1; //ONE = +ve, TWO = -ve;
            Random rand = new Random();

            jitter = Helper.RandomClamp(rand, -1, 1);

            thetaInRadians = (float)Math.Round(self.Rotation_Max * jitter, 2);

            //Return a random slightly off, (either for X-axis or Y-axis) position
            return ((float)Math.Round(thetaInRadians, 2));
        }
    }//class
}//namespace
